/* Copyright 2012 Oliver Katz

    This file is part of Theta.

    Theta is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include "parsing.h"

namespace parsing
{
	ParsingErrorBuffer::ParsingErrorBuffer()
	{
		linenum = 0;
	}

	void ParsingErrorBuffer::setLineNumber(int n)
	{
		linenum = n;
	}

	void ParsingErrorBuffer::setFileName(string f)
	{
		file = f;
	}

	void ParsingErrorBuffer::error(string msg, string chunk, string line)
	{
		stringstream ss;
		ss << "error (" << file << ":" << linenum << ")\n  " << msg << " (" << chunk << ")\n  " << line << "\n\n";
		errors.push_back(ss.str());
	}

	void ParsingErrorBuffer::warning(string msg, string chunk, string line)
	{
		stringstream ss;
		ss << "warning (" << file << ":" << linenum << ")\n  " << msg << " (" << chunk << ")\n  " << line << "\n\n";
		warnings.push_back(ss.str());
	}

	void ParsingErrorBuffer::dump()
	{
		for (vector<string>::iterator i = errors.begin(); i != errors.end(); i++)
			cout << *i;
		for (vector<string>::iterator i = warnings.begin(); i != warnings.end(); i++)
			cout << *i;
		if (errors.size() == 1)
			cout << "1 error.\n";
		else if (errors.size() > 0)
			cout << errors.size() << " errors";
		if (warnings.size() == 1)
			cout << "1 warning.\n";
		else if (warnings.size() > 0)
			cout << " and " << warnings.size() << " warnings.";
		else if (errors.size() != 0 || warnings.size() != 0)
			cout << ".";
		if (errors.size() != 0 || warnings.size() != 0)
			cout << "\n";

		if (!errors.empty())
			_exit(1);
	}

	const string illegal = "`";
	const string delims = "~!@#$%^&*()-+=[{]}|;:,<>/? \t\n";
	const string stoplist = " \t\n";

	vector<string> tokenize(string s, ParsingErrorBuffer &b)
	{
		bool q = false;
		vector<string> rtn;
		string buf;

		for (string::iterator i = s.begin(); i != s.end(); i++)
		{
			if (*i == '"' || *i == '\'')
			{
				if (q == false)
				{
					if (!buf.empty())
					{
						rtn.push_back(buf);
						buf = "";
					}
					q = true;
				}
				else
				{
					q = false;
					buf += *i;
					rtn.push_back(buf);
					buf = "";
					continue;
				}
			}

			if (q == true)
			{
				buf += *i;
				continue;
			}

			if (illegal.find(*i) != string::npos)
			{
				stringstream ss;
				ss << *i;
				b.error("illegal character", ss.str(), s);
				break;
			}

			if (delims.find(*i) != string::npos)
			{
				if (!buf.empty())
				{
					rtn.push_back(buf);
					buf = "";
				}

				if (stoplist.find(*i) == string::npos)
				{
					buf += *i;
					rtn.push_back(buf);
					buf = "";
				}
			}
			else
			{
				buf += *i;
			}
		}

		if (!buf.empty())
		{
			rtn.push_back(buf);
		}

		return rtn;
	}

	Clause::Clause()
	{
	}

	Clause::Clause(string n)
	{
		name = n;
	}

	void Clause::addArgument(string a)
	{
		args.push_back(a);
	}

	int Clause::getArgumentSize()
	{
		return args.size();
	}

	void Clause::addCfgLine(vector<string> cl)
	{
		vector<string> tmp(cl.begin()+1, cl.end());
		cfg[cl[0]] = tmp;
	}

	void Clause::addFrame(Frame f)
	{
		frame = f;
	}

	string Clause::display()
	{
		stringstream ss;
		ss << name;
		for (vector<string>::iterator i = args.begin(); i != args.end(); i++)
			ss << " " << *i;
		ss << "\n";
		for (map<string, vector<string> >::iterator i = cfg.begin(); i != cfg.end(); i++)
		{
			ss << ":: '" << i->first << "'";
			for (vector<string>::iterator j = i->second.begin(); j != i->second.end(); j++)
			{
				ss << " '" << *j << "'";
			}
			ss << "\n";
		}
		ss << "= " << frame.display() << "\n";
		return ss.str();
	}

	vector<string> Clause::getPlugins()
	{
		vector<string> rtn;
		for (map<string, vector<string> >::iterator i = cfg.begin(); i != cfg.end(); i++)
		{
			rtn.push_back(i->first);
		}
		return rtn;
	}

	vector<string> Clause::getCfg(string plugin)
	{
		if (cfg.find(plugin) == cfg.end())
		{
			vector<string> rtn;
			return rtn;
		}
		else
			return cfg[plugin];
	}

	Frame Clause::getFrame()
	{
		return frame;
	}

	bool Parser::isFunction(string s)
	{
		if (clauses.find(s) == clauses.end())
			return false;
		return true;
	}

	bool Parser::isSymbol(string s)
	{
		string legal = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.";
		for (string::iterator i = s.begin(); i != s.end(); i++)
			if (legal.find(*i) == string::npos)
				return false;
		return true;
	}

	bool Parser::isValue(string s)
	{
		if (s[0] == '"' && s[s.size()] == '"')
			return true;

		const string digits = "0123456789.";
		for (string::iterator i = s.begin(); i != s.end(); i++)
		{
			if (digits.find(*i) == string::npos)
				return false;
		}

		return true;
	}

	bool Parser::isSeparator(string s)
	{
		if (s.compare(",") == 0)
			return true;
		return false;
	}

	bool Parser::isLeftBracket(string s)
	{
		if (s.compare("(") == 0)
			return true;
		return false;
	}

	bool Parser::isRightBracket(string s)
	{
		if (s.compare(")") == 0)
			return true;
		return false;
	}

	bool Parser::isOperator(string s)
	{
		if (s.compare("+") == 0)
			return true;
		else if (s.compare("-") == 0)
			return true;
		else if (s.compare("*") == 0)
			return true;
		else if (s.compare("/") == 0)
			return true;
		else if (s.compare("%") == 0)
			return true;
		else
			return false;
	}

	int Parser::branches(string s)
	{
		if (isFunction(s))
			return clauses[s].getArgumentSize();
		else if (isOperator(s))
			return 2;
		else
			return 0;
	}

	vector<string> Parser::splitLines(string page)
	{
		vector<string> rtn;
		string buf;

		for (string::iterator i = page.begin(); i != page.end(); i++)
		{
			if (*i == '\n')
			{
				rtn.push_back(buf);
				buf = "";
			}
			else
			{
				buf += *i;
			}
		}

		rtn.push_back(buf);
		return rtn;
	}

	vector<string> Parser::shuntingYard(vector<string> toks, string line)
	{
		vector<string> stack;
		vector<string> queue;

		for (vector<string>::iterator i = toks.begin(); i != toks.end(); i++)
		{
			if (isValue(*i) || isSymbol(*i))
			{
				queue.push_back(*i);
			}
			else if (isFunction(*i) && isSymbol(*i))
			{
				queue.push_back(*i);
			}
			else if (isSeparator(*i))
			{
				while (!isLeftBracket(stack.back()))
				{
					queue.push_back(stack.back());
					stack.pop_back();

					if (stack.empty())
					{
						pebble.error("mismatched parenthesis", "", line);
						break;
					}
				}
			}
			else if (isOperator(*i))
			{
				while (!stack.empty() && isOperator(stack.back()))
				{
					queue.push_back(stack.back());
					stack.pop_back();
				}
				stack.push_back(*i);
			}
			else if (isLeftBracket(*i))
			{
				stack.push_back(*i);
			}
			else if (isRightBracket(*i))
			{
				while (!stack.empty() && !isLeftBracket(stack.back()))
				{
					queue.push_back(stack.back());
					stack.pop_back();
				}

				if (stack.empty())
				{
					pebble.error("mismatched parenthesis", "", line);
				}

				stack.pop_back();
				if (isFunction(stack.back()))
				{
					queue.push_back(stack.back());
					stack.pop_back();
				}
			}
			else
			{
				pebble.error("unexpected symbol", *i, line);
			}
		}

		while (!stack.empty())
		{
			if (isLeftBracket(stack.back()) || isRightBracket(stack.back()))
			{
				pebble.error("mismatched parenthesis", "", line);
			}
			else
			{
				queue.push_back(stack.back());
				stack.pop_back();
			}
		}

		return queue;
	}

	Frame Parser::treeify(vector<string> rpn, string line)
	{
		Frame f = Frame(rpn.back());
		int n = 0;
		for (int i = 0; i < branches(rpn.back()); i++)
		{
			if (n+1 >= (int)rpn.size())
			{
				pebble.error("function called with improper number of arguments", rpn.back(), line);
				break;
			}

			vector<string> buf(rpn.begin(), rpn.begin()+(rpn.size()-n-1));
			Frame tmp = treeify(buf, line);
			n += tmp.calcSize();
			f.branch(tmp);
		}
		return f;
	}

	Parser::Parser()
	{
		pebble = ParsingErrorBuffer();
	}

	string Parser::readFile(string path)
	{
		pebble.setFileName(path);
		ifstream f(path.c_str());
		string str((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
		return str;
	}

	map<string, Clause> Parser::parse(string page)
	{
		vector<string> lines = splitLines(page);
		vector<vector<string> > cfgbuf;

		for (int i = 0; i < (int)lines.size(); i++)
		{
			pebble.setLineNumber(i);
			if (lines[i].empty())
				continue;
			else if (lines[i][0] == ':' && lines[i][1] == ':')
			{
				cfgbuf.push_back(tokenize(lines[i].substr(2), pebble));
			}
			else if (lines[i].find("=") != string::npos)
			{
				vector<string> toks = tokenize(lines[i], pebble);
				Clause c = Clause(toks[0]);
				clauses[toks[0]] = c;
				for (vector<vector<string> >::iterator j = cfgbuf.begin(); j != cfgbuf.end(); j++)
				{
					c.addCfgLine(*j);
				}
				cfgbuf.clear();

				int as = 1;
				for (; as < (int)toks.size(); as++)
				{
					if (toks[as].compare("=") == 0)
						break;
					c.addArgument(toks[as]);
				}

				vector<string> tmp(toks.begin()+as+1, toks.end());
				tmp = shuntingYard(tmp, lines[i]);

				Frame f = treeify(tmp, lines[i]);
				c.addFrame(f);

				clauses[toks[0]] = c;
			}
		}

		if (!cfgbuf.empty())
			pebble.error("leftover configuration lines in buffer", "", lines[1]);

		pebble.dump();
		return clauses;
	}
}